#include "zjson.h"
#include "z_terminal.h"
static struct {
    jval true,false,null;
}ZJSON_CONST_VALUE = {
        {.data.vbool=1,.type=BOL},
        {.data.vbool=0,.type=BOL},
        {.data.vint=0,.type=NUL}
};
char *zJSON_strdup(const char *s) {
	if (!s) return NULL;
    size_t len = strlen(s);
    char *news = (char*)malloc(sizeof(char)*len+1);
    memcpy(news,s,len);
    news[len] = 0;
    return news;
}
/*
static void *(memset) (void *s,int c,size_t n)
{
	if (!s) return NULL;
    const unsigned char uc = c;
    unsigned char *su;
    for(su = s;0 < n;++su,--n) *su = uc;
    return s;
}
static size_t strlen(const jchar * ptr) {
    size_t len = 0;
    while (ptr&&*ptr++)len++;
    return len;
}

static int strcmp(const jchar * ptr1, const jchar * ptr2) {
    size_t len = strlen(ptr1),i = 0;
    if (len != strlen(ptr2)) return 1;
    for (; i != len; i++) if (*ptr1 != *ptr2) return 1;
    return 0;
}
*/

/* Here is a weird memory bug. 
 * Valgrind will report definitely memory leak 
 *   if put the free statement in these if-blocks.*/
void zJSON_free_jval(jval *node) {
    assert(node);
    if (node->type == OBJ) {
        list_foreach_safe(zJSON_get_obj(node), itr) {
            jpair *entry = list_entry(itr, jpair, list);
            zJSON_free_jpair(entry);
        }
        free_list_head(zJSON_get_obj(node));
    } else
    if (node->type == ARR) {
        list_foreach_safe(zJSON_get_arr(node), itr) {
            jelem *entry = list_entry(itr, jelem, list);
            zJSON_free_jelem(entry);
        }
        free_list_head(zJSON_get_arr(node));
    } else
    if (node->type == STR) {
        zJSON_free_jstr(node->data.vstr);
    } else
	/* No BOL|NUL since they are constraints */
	if (node->type != NUL && node->type != BOL)
		free(node);
}

jval *zJSON_new_jval(vtype type) {
	/* No need to create a new BOL|NUL */
    jval *v = (jval *) malloc(sizeof(jval));
    assert(v);
    v->type = type;
    if (type == OBJ || type == ARR) {
        v->data.varr = new_list_head(); //Union
        assert(v->data.varr);
    } else 
        v->data.vint = 0;
    return v;
}
jval *zJSON_new_int(jint n) {jval * v = zJSON_new_jval(NUM); v->data.vdouble = (double)n;return v;}
jval *zJSON_new_double(jdbl n) {jval * v = zJSON_new_jval(DBL); v->data.vdouble = n; return v;}
jval *zJSON_new_true() {return &ZJSON_CONST_VALUE.true;}
jval *zJSON_new_false() {return &ZJSON_CONST_VALUE.false;}
jval *zJSON_new_null() {return &ZJSON_CONST_VALUE.null;}
jval *zJSON_new_string(jstr s) { jval * v = zJSON_new_jval(STR); v->data.vstr = zJSON_strdup(s); return v; }


void zJSON_free_jpair(jpair *pair) {
    assert(pair);
    zJSON_free_jval(pair->val);
    zJSON_free_jstr(pair->key);//memory leak here..
    free(pair);
}

void zJSON_free_jpair_CS(jpair *pair) {
    assert(pair);
    zJSON_free_jval(pair->val);
    free(pair);
}

jpair *zJSON_new_jpair(jchar *key, jval *val) {
    jpair *pair = (jpair *) malloc(sizeof(jpair));
    assert(pair);
    pair->key = key;
    pair->val = val;
    return pair;
}

jpair *zJSON_new_jpair_CS(jchar *key, jval *val) {
    jpair *pair = (jpair *) malloc(sizeof(jpair));
    assert(pair);
    pair->key = zJSON_strdup(key);
    pair->val = val;
    return pair;
}

void zJSON_free_jelem(jelem *elem) {
    assert(elem);
    zJSON_free_jval(elem->val);
    free(elem);
}

jelem *zJSON_new_jelem(jval *val) {
    jelem *elem = (jelem *) malloc(sizeof(jelem));
    assert(elem);
    elem->val = val;
    return elem;
}

void zJSON_free_jstr(jstr str) {
    assert(str);
    free(str);
}

jstr zJSON_get_str(jval *node) {
    assert(node);
    assert(node->type == STR);
    return node->data.vstr;
}

jint zJSON_get_int(jval *node) {
    assert(node);
    assert(node->type == NUM);
    return (int) node->data.vdouble;
}

jdbl zJSON_get_dbl(jval *node) {
    assert(node);
    assert(node->type == DBL);
    return node->data.vdouble;
}

jint zJSON_get_bol(jval *node) {
    assert(node);
    assert(node->type == BOL);
    return node->data.vbool;
}

jarr zJSON_get_arr(jval *node) {
    assert(node);
    assert(node->type == ARR);
    return node->data.varr;
}

jobj zJSON_get_obj(jval *node) {
    assert(node);
    assert(node->type == OBJ);
    return node->data.vobj;
}

void zJSON_set_type(jval *node, vtype type) {
    assert(node);
    node->type = type;
}

int zJSON_has_key(jval *obj, const jchar *key) {
    assert(obj);
    assert(obj->type == OBJ);
    __list_foreach_entry_reverse(obj->data.vobj, jpair, list, itr) {
        if (!strcmp(itr->key, key)) { return 1; }
    }
    return 0;
}

void zJSON_set_key(jval *node, jchar *key) {
    if (zJSON_has_key(node, key))
        return;
    struct list_head *head = node->data.vobj;
    jpair *pair = zJSON_new_jpair(key, NULL);
    list_add(head, &pair->list);
}

void zJSON_set_key_value(jval *node, const jchar *key, jval *val) {
    assert(zJSON_has_key(node, key));
    __list_foreach_entry(node->data.vobj, jpair, list, itr) {
        if (!strcmp(itr->key, key)) { itr->val = val; return; }
    }
}

void zJSON_set_arr_value(jval *node, jval *val) {
    assert(node);
    assert(node->type == ARR);
    jelem *elem = zJSON_new_jelem(val);
    list_add(node->data.varr, &elem->list);
}

size_t zJSON_get_arr_length(jval *arr) {
    assert(arr);
    assert(arr->type == ARR);
    return list_length(arr->data.varr);
}

size_t zJSON_get_obj_length(jval *obj) {
    assert(obj);
    assert(obj->type == OBJ);
    return list_length(obj->data.vobj);
}

static jval *_zJSON_get_arr_item(jval *arr, unsigned int idx) {
    assert(zJSON_get_arr_length(arr) > idx);
    size_t i = 0;
    list_foreach_prev(arr->data.varr, itr) {
        if (i++ == idx) { return list_entry(itr, jelem, list)->val; }
    }
    return NULL;
}

jval *zJSON_get_arr_item(jval *arr, int idx) {
	return _zJSON_get_arr_item(arr, idx<0?(zJSON_get_arr_length(arr)+idx):idx);
}

jval *zJSON_get_key_val(jval *obj, const jchar *key) {
    assert(zJSON_has_key(obj, key));
    __list_foreach_entry_reverse(obj->data.vobj, jpair, list, itr) {
        if (!strcmp(itr->key, key)) { return itr->val; }
    }
    return NULL;
}

/* Note:
 * - If the key is a constant,
 *     calling the function WITH a suffix "_CS"
 *     would raise a segment fault.
 * - Otherwise if the key is an allocated c-string,
 *     calling the function WITHOUT a suffix "_CS"
 *     would make a memory leak.
 * */
static void _zJSON_del_obj_key(jval *obj, const char *key, int CS_flag) {
    struct list_head *listItem = NULL;
            list_foreach_entry(obj->data.vobj, jpair, list, itr) {
        if (!strcmp(itr->key, key)) { listItem = &itr->list; break; }
    }
    __list_del(listItem->prev, listItem->next);
    if (CS_flag)
        zJSON_free_jpair_CS(list_entry(listItem, jpair, list)); //This key is an allocated c-string.
    else
        zJSON_free_jpair(list_entry(listItem, jpair, list)); //This key is an allocated c-string.
}

void zJSON_del_key_CS(jval *obj, const char *key) {
    _zJSON_del_obj_key(obj, key, 1);
}

void zJSON_del_key(jval *obj, char *key) {
    _zJSON_del_obj_key(obj, key, 0);
}

/* Note:
 * - If you are maintaining a jval * ptr point to
 *     the item which would be deleted/replaced, the ptr
 *     would be discarded since the ptr would be free()
 *     in this function.
 * - If you wanna go on maintaining it, you should
 *     dump it by zJSON_dump()
 */
void zJSON_rpl_key_val(jval *obj, const char *key, jval *newValue) {
    jval *node = zJSON_get_key_val(obj, key);
    zJSON_free_jval(node);
    zJSON_set_key_value(obj, key, newValue);
}

void zJSON_del_arr_item(jval *arr, unsigned int idx) {
    struct list_head *listItem = NULL;
    unsigned int i = 0;
    list_foreach_prev(arr->data.varr, itr) {
        if (i++ == idx) { listItem = itr; break; }
    }
    __list_del(listItem->prev, listItem->next);
    zJSON_free_jelem(list_entry(listItem, jelem, list));
}

void zJSON_rpl_arr_item(jval *arr, unsigned int idx, jval *newValue) {
    zJSON_del_arr_item(arr, idx);
    zJSON_set_arr_value(arr, newValue);
}

#define ZJSON_SPACE(n) do{if(prettify_flag)for(size_t i = 0; i < n; ++i)printf(" ");}while(0)
#define ZJSON_NEWLINE() do{if(prettify_flag)printf("\n");}while(0)
#define ZJSON_COLORFY(color) do{if(color_flag)_PCOLOR(color);}while(0)

void zJSON_render(jval *root,
                   const int prettify_flag, size_t indent, size_t indentStep,
                   int color_flag,
                   int braces_flag) {
    assert(root);
    if (root->type == STR) {
        ZJSON_COLORFY(_RED);
        printf("\"%s\"", zJSON_get_str(root));
        ZJSON_COLORFY(_CLOSE);
    } else if (root->type == NUM) {
        ZJSON_COLORFY(_GREEN);
        printf("%d", zJSON_get_int(root));
        ZJSON_COLORFY(_CLOSE);
    } else if (root->type == DBL) {
        ZJSON_COLORFY(_GREEN);
        printf("%lf", zJSON_get_dbl(root));
        ZJSON_COLORFY(_CLOSE);
    } else if (root->type == NUL) {
        ZJSON_COLORFY(_SKY);
        printf("null");
        ZJSON_COLORFY(_CLOSE);
    } else if (root->type == BOL) {
        ZJSON_COLORFY(_SKY);
        printf(root->data.vbool ? "true" : "null");
        ZJSON_COLORFY(_CLOSE);
    } else if (root->type == ARR) {
        ZJSON_COLORFY(_HIGHLIGHT);
        printf(braces_flag ? "[" : " ");
        ZJSON_COLORFY(_CLOSE);
        if (zJSON_get_arr_length(root) && zJSON_get_arr_item(root, 0)->type == OBJ)
            ZJSON_NEWLINE();
        indent += indentStep;
        zJSON_arr_for_each_elem(root, itr) {
            if (zJSON_get_arr_item(root, 0)->type == OBJ) ZJSON_SPACE(indent);
            zJSON_render(itr->val, prettify_flag, indent, indentStep, color_flag, braces_flag);
            if (itr->list.prev != zJSON_get_arr(root)) printf(",");
            if (zJSON_get_arr_item(root, 0)->type == OBJ) ZJSON_NEWLINE();
            else if (itr->list.prev != zJSON_get_arr(root)) ZJSON_SPACE(1);
        }
        indent -= indentStep;
        if (zJSON_get_arr_length(root) && zJSON_get_arr_item(root, 0)->type == OBJ)
            ZJSON_SPACE(indent);
        ZJSON_COLORFY(_HIGHLIGHT);
        printf(braces_flag ? "]" : " ");
        ZJSON_COLORFY(_CLOSE);
    } else if (root->type == OBJ) {
        ZJSON_COLORFY(_HIGHLIGHT);
        printf(braces_flag ? "{" : " ");
        ZJSON_COLORFY(_CLOSE);
        if (zJSON_get_obj_length(root))
            ZJSON_NEWLINE();
        indent += indentStep;
        zJSON_obj_for_each_pair(root, itr) {
            ZJSON_SPACE(indent);
            ZJSON_COLORFY(_YELLOW);
            printf((prettify_flag) ? "\"%s\" : " : "\"%s\":", itr->key);
            ZJSON_COLORFY(_CLOSE);
            zJSON_render(itr->val, prettify_flag, indent, indentStep, color_flag, braces_flag);
            if (itr->list.prev != zJSON_get_obj(root))
                printf(",");
            ZJSON_NEWLINE();
        }
        indent -= indentStep;
        if (!list_empty(zJSON_get_obj(root)))
            ZJSON_SPACE(indent);
        ZJSON_COLORFY(_HIGHLIGHT);
        printf(braces_flag ? "}" : " ");
        ZJSON_COLORFY(_CLOSE);
    }
}

void zJSON_minify(jval *root) {
    zJSON_render(root, 0, 0, 0, 0, 1);
}

void zJSON_prettify(jval *root, size_t indentStep) {
    zJSON_render(root, 1, 0, indentStep, 0, 1);
}

void zJSON_colorfy(jval *root) {
    zJSON_render(root, 1, 0, 4, 1, 1);
}

